﻿#region Header
//
//	Project:		AllegroNET
//	Description:	Silverlight port of Allegro (http://alleg.sourceforge.net/)
//
//	Routines for drawing primitives (lines, circles, rectangles and etc)
//
//	Copyright © 2010 Andrey Gayvoronsky
//
//	Please read the License.txt for details.
//
#endregion

using System;
using System.Collections.Generic;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace AllegroNET
{
	public delegate void draw_callback(BITMAP bmp, int x, int y, int c);

	public partial class Allegro
	{
		/* clear_bitmap:
		 *  Clears the bitmap to color 0.
		 */
		static public void clear_bitmap(BITMAP bitmap)
		{
			clear_to_color(bitmap, 0);
		}

		/* clear_to_color:
		 *   Fills a bitmap with the specified color.
		 */
		static public void clear_to_color(BITMAP bitmap, int color)
		{
			if (bitmap == null)
				return;
			try
			{
				int ofs1 = 0;
				int ofs2 = ofs1 + bitmap.w - 1;
				
				do
				{
					bitmap.dat[ofs2] = color;
				} while (--ofs2 >= ofs1);

				int size_of_rgb = sizeof(int);
				int len = bitmap.w * size_of_rgb;
				int y1 = 0;
				int y2 = bitmap.h - 1;
				
				ofs2 = len;
				while (++y1 <= y2)
				{
					Buffer.BlockCopy(bitmap.dat, ofs1, bitmap.dat, ofs2, len);
					ofs2 += len;
				}
			}
			catch (Exception e)
			{
				System.Windows.MessageBox.Show(e.Message);
			}
		}

		/* putpixel:
		 *  Draws a pixel onto a bitmap.
		 */
		static public void putpixel(BITMAP bmp, int x, int y, int color)
		{
			if ((x < 0) || (x >= bmp.w) || (y < 0) || (y >= bmp.h))
				return;

			bmp.dat[bmp.line[y] + x] = color;
		}

		/* getpixel:
		 *  Reads a pixel from a bitmap.
		 */
		static public int getpixel(BITMAP bmp, int x, int y)
		{
			if ((x < 0) || (x >= bmp.w) || (y < 0) || (y >= bmp.h))
				return 0;

			return bmp.dat[bmp.line[y] + x];
		}

		static private void hfill(BITMAP dst, int dx1, int dy, int dx2, int color, bool clip)
		{
			if (dst == null)
				return;

			if (dx1 > dx2)
			{
				int tmp = dx1;
				dx1 = dx2;
				dx2 = tmp;
			}

			if (clip)
			{
				if (dx1 < dst.cl)
					dx1 = dst.cl;

				if (dx2 >= dst.cr)
					dx2 = dst.cr - 1;

				if ((dx1 > dx2) || (dy < dst.ct) || (dy >= dst.cb))
					return;
			}

			int ofs1 = dst.line[dy] + dx1;
			int ofs2 = ofs1 + (dx2 - dx1);

			do
			{
				dst.dat[ofs2] = color;
			} while (--ofs2 >= ofs1);
		}

		/* rectfill:
		 *  Draws a solid filled rectangle.
		 */
		static public void rectfill(BITMAP bmp, int x1, int y1, int x2, int y2, int color)
		{
			int t;

			if (bmp == null)
				return;

			if (y1 > y2)
			{
				t = y1;
				y1 = y2;
				y2 = t;
			}

			if (x1 > x2)
			{
				t = x1;
				x1 = x2;
				x2 = t;
			}

			if (bmp.clip)
			{
				if (x1 < bmp.cl)
					x1 = bmp.cl;

				if (x2 >= bmp.cr)
					x2 = bmp.cr - 1;

				if (x2 < x1)
					return;

				if (y1 < bmp.ct)
					y1 = bmp.ct;

				if (y2 >= bmp.cb)
					y2 = bmp.cb - 1;

				if (y2 < y1)
					return;
			}

			int ofs1 = bmp.line[y1] + x1;
			int ofs2 = ofs1 + (x2 - x1);

			do
			{
				bmp.dat[ofs2] = color;
			} while (--ofs2 >= ofs1);

			int size_of_rgb = sizeof(int);
			int len = (x2 - x1 + 1) * size_of_rgb;

			ofs1 = bmp.line[y1] * size_of_rgb + x1 * size_of_rgb;
			ofs2 = ofs1 + len;
			int step_y = bmp.w * size_of_rgb;

			while (++y1 <= y2)
			{
				Buffer.BlockCopy(bmp.dat, ofs1, bmp.dat, ofs2, len);
				ofs2 += step_y;
			}
		}

		/* rect:
		 *  Draws an outline rectangle.
		 */
		static public void rect(BITMAP bmp, int x1, int y1, int x2, int y2, int color)
		{
			int t;

			if (x2 < x1)
			{
				t = x1;
				x1 = x2;
				x2 = t;
			}

			if (y2 < y1)
			{
				t = y1;
				y1 = y2;
				y2 = t;
			}

			hline(bmp, x1, y1, x2, color);

			if (y2 > y1)
				hline(bmp, x1, y2, x2, color);

			if (y2 - 1 >= y1 + 1)
			{
				vline(bmp, x1, y1 + 1, y2 - 1, color);

				if (x2 > x1)
					vline(bmp, x2, y1 + 1, y2 - 1, color);
			}
		}

		/* do_line:
		 * Calculates all the points along a line between x1, y1 and x2, y2, 
		 * calling the supplied function for each one. This will be passed a 
		 * copy of the bmp parameter, the x and y position, and a copy of the 
		 * d parameter (so do_line() can be used with putpixel()).
		 * 
		 * DDA Line Algorithm is using 
		 */
		static public void do_line(BITMAP bmp, int x1, int y1, int x2, int y2, int color, draw_callback callback)
		{
			int length, i;
			double x, y;
			double xincrement;
			double yincrement;

			length = System.Math.Abs(x2 - x1);

			if (System.Math.Abs(y2 - y1) > length)
				length = System.Math.Abs(y2 - y1);

			xincrement = (double)(x2 - x1) / (double)length;
			yincrement = (double)(y2 - y1) / (double)length;

			x = x1 + 0.5;
			y = y1 + 0.5;

			for (i = 1; i <= length; ++i)
			{
				callback(bmp, (int)x, (int)y, color);
				x = x + xincrement;
				y = y + yincrement;
			}
		}

		/* line:
		 *  Draws a line from x1, y1 to x2, y2, using putpixel() to do the work.
		 */
		static public void line(BITMAP bmp, int x1, int y1, int x2, int y2, int color)
		{
			do_line(bmp, x1, y1, x2, y2, color, putpixel);
		}

		/* vline:
		 *  Draws a vertical line onto a bitmap.
		 */
		static public void vline(BITMAP dst, int dx, int dy1, int dy2, int color)
		{
			if (dst == null)
				return;

			if (dy1 > dy2)
			{
				int tmp = dy1;
				dy1 = dy2;
				dy2 = tmp;
			}

			if (dst.clip)
			{
				if (dy1 < dst.ct)
					dy1 = dst.ct;

				if (dy2 >= dst.cb)
					dy2 = dst.cb - 1;

				if ((dx < dst.cl) || (dx >= dst.cr) || (dy1 > dy2))
					return;
			}

			int ofs = dst.line[dy1] + dx;
			int w = dst.w;

			for (int y = dy1; y <= dy2; y++, ofs += w)
				dst.dat[ofs] = color;
		}

		/* hline:
		 *  Draws a horizontal line onto a bitmap.
		 */
		static public void hline(BITMAP dst, int dx1, int dy, int dx2, int color)
		{
			if (dst == null)
				return;

			if (dst.clip)
			{
				if (dx1 < dst.cl)
					dx1 = dst.cl;

				if (dx2 >= dst.cr)
					dx2 = dst.cr - 1;

				if ((dx1 > dx2) || (dy < dst.ct) || (dy >= dst.cb))
					return;
			}

			int w = dx2 - dx1;
			int ofs = dst.line[dy] + dx1;

			do
			{
				dst.dat[ofs++] = color;
			} while (--w >= 0);
		}

		static public void triangle(BITMAP bmp, int x1, int y1, int x2, int y2, int x3, int y3, int color)
		{
			line(bmp, x1, y1, x2, y2, color);
			line(bmp, x2, y2, x3, y3, color);
			line(bmp, x3, y3, x1, y1, color);
		}

		/* do_circle:
		 *  Helper function for the circle drawing routines. Calculates the points
		 *  in a circle of radius r around point x, y, and calls the specified 
		 *  routine for each one. The output proc will be passed first a copy of
		 *  the bmp parameter, then the x, y point, then a copy of the d parameter
		 */
		static public void do_circle(BITMAP bmp, int x, int y, int radius, int d, draw_callback callback)
		{
			int cx = 0;
			int cy = radius;
			int df = 1 - radius;
			int d_e = 3;
			int d_se = -2 * radius + 5;

			do
			{
				callback(bmp, x + cx, y + cy, d);

				if (cx != 0)
					callback(bmp, x - cx, y + cy, d);

				if (cy != 0)
					callback(bmp, x + cx, y - cy, d);

				if ((cx != 0) && (cy != 0))
					callback(bmp, x - cx, y - cy, d);

				if (cx != cy)
				{
					callback(bmp, x + cy, y + cx, d);

					if (cx != 0)
						callback(bmp, x + cy, y - cx, d);

					if (cy != 0)
						callback(bmp, x - cy, y + cx, d);

					if (cx != 0 && cy != 0)
						callback(bmp, x - cy, y - cx, d);
				}

				if (df < 0)
				{
					df += d_e;
					d_e += 2;
					d_se += 2;
				}
				else
				{
					df += d_se;
					d_e += 2;
					d_se += 4;
					cy--;
				}

				cx++;

			} while (cx <= cy);
		}

		/* circle:
		 *  Draws a circle.
		 */
		static public void circle(BITMAP bmp, int x, int y, int radius, int color)
		{
			int sx, sy, dx, dy;

			if (bmp == null)
				return;

			if (bmp.clip)
			{
				sx = x - radius - 1;
				sy = y - radius - 1;
				dx = x + radius + 1;
				dy = y + radius + 1;

				if ((sx >= bmp.cr) || (sy >= bmp.cb) || (dx < bmp.cl) || (dy < bmp.ct))
					return;
			}

			do_circle(bmp, x, y, radius, color, putpixel);
		}

		/* circlefill:
		 *  Draws a filled circle.
		 */
		static public void circlefill(BITMAP bmp, int x, int y, int radius, int color)
		{
			int cx = 0;
			int cy = radius;
			int df = 1 - radius;
			int d_e = 3;
			int d_se = -2 * radius + 5;
			int sx, sy, dx, dy;
			bool clip;

			if (bmp == null)
				return;

			clip = bmp.clip;

			if (clip)
			{
				sx = x - radius - 1;
				sy = y - radius - 1;
				dx = x + radius + 1;
				dy = y + radius + 1;

				if ((sx >= bmp.cr) || (sy >= bmp.cb) || (dx < bmp.cl) || (dy < bmp.ct))
					return;

				if ((sx >= bmp.cl) && (sy >= bmp.ct) && (dx < bmp.cr) && (dy < bmp.cb))
					clip = false;
			}

			do
			{
				hfill(bmp, x - cy, y - cx, x + cy, color, clip);

				if (cx != 0)
					hfill(bmp, x - cy, y + cx, x + cy, color, clip);

				if (df < 0)
				{
					df += d_e;
					d_e += 2;
					d_se += 2;
				}
				else
				{
					if (cx != cy)
					{
						hfill(bmp, x - cx, y - cy, x + cx, color, clip);

						if (cy != 0)
							hfill(bmp, x - cx, y + cy, x + cx, color, clip);
					}

					df += d_se;
					d_e += 2;
					d_se += 4;
					cy--;
				}

				cx++;

			} while (cx <= cy);
		}

		/* do_ellipse:
		 *  Helper function for the ellipse drawing routines. Calculates the points
		 *  in an ellipse of radius rx and ry around point x, y, and calls the 
		 *  specified routine for each one. The output proc will be passed first a 
		 *  copy of the bmp parameter, then the x, y point, then a copy of the d 
		 *  parameter (so putpixel() can be used as the callback).
		 */
		static public void do_ellipse(BITMAP bmp, int x, int y, int rx, int ry, int d, draw_callback callback)
		{
			if (bmp == null)
				return;

			int ix, iy;
			int h, i, j, k;
			int oh, oi, oj, ok;

			if (rx < 1)
				rx = 1;

			if (ry < 1)
				ry = 1;

			h = i = j = k = 0xFFFF;

			if (rx > ry)
			{
				ix = 0;
				iy = rx * 64;

				do
				{
					oh = h;
					oi = i;
					oj = j;
					ok = k;

					h = (ix + 32) >> 6;
					i = (iy + 32) >> 6;
					j = (h * ry) / rx;
					k = (i * ry) / rx;

					if (((h != oh) || (k != ok)) && (h < oi))
					{
						callback(bmp, x + h, y + k, d);
						if (h != 0)
							callback(bmp, x - h, y + k, d);
						if (k != 0)
						{
							callback(bmp, x + h, y - k, d);
							if (h != 0)
								callback(bmp, x - h, y - k, d);
						}
					}

					if (((i != oi) || (j != oj)) && (h < i))
					{
						callback(bmp, x + i, y + j, d);
						if (i != 0)
							callback(bmp, x - i, y + j, d);
						if (j != 0)
						{
							callback(bmp, x + i, y - j, d);
							if (i != 0)
								callback(bmp, x - i, y - j, d);
						}
					}

					ix = ix + iy / rx;
					iy = iy - ix / rx;

				} while (i > h);
			}
			else
			{
				ix = 0;
				iy = ry * 64;

				do
				{
					oh = h;
					oi = i;
					oj = j;
					ok = k;

					h = (ix + 32) >> 6;
					i = (iy + 32) >> 6;
					j = (h * rx) / ry;
					k = (i * rx) / ry;

					if (((j != oj) || (i != oi)) && (h < i))
					{
						callback(bmp, x + j, y + i, d);
						if (j != 0)
							callback(bmp, x - j, y + i, d);
						if (i != 0)
						{
							callback(bmp, x + j, y - i, d);
							if (j != 0)
								callback(bmp, x - j, y - i, d);
						}
					}

					if (((k != ok) || (h != oh)) && (h < oi))
					{
						callback(bmp, x + k, y + h, d);
						if (k != 0)
							callback(bmp, x - k, y + h, d);
						if (h != 0)
						{
							callback(bmp, x + k, y - h, d);
							if (k != 0)
								callback(bmp, x - k, y - h, d);
						}
					}

					ix = ix + iy / ry;
					iy = iy - ix / ry;

				} while (i > h);
			}

		}

		/* ellipse:
		 *  Draws an ellipse.
		 */
		static public void ellipse(BITMAP bmp, int x, int y, int rx, int ry, int color)
		{
			int sx, sy, dx, dy;

			if (bmp == null)
				return;

			if (bmp.clip)
			{
				sx = x - rx - 1;
				sy = y - ry - 1;
				dx = x + rx + 1;
				dy = y + ry + 1;

				if ((sx >= bmp.cr) || (sy >= bmp.cb) || (dx < bmp.cl) || (dy < bmp.ct))
					return;
			}

			do_ellipse(bmp, x, y, rx, ry, color, putpixel);
		}

		/* ellipsefill:
		 *  Draws a filled ellipse.
		 */
		static public void ellipsefill(BITMAP bmp, int x, int y, int rx, int ry, int color)
		{
			int ix, iy;
			int a, b, c, d;
			int da, db, dc, dd;
			int na, nb, nc, nd;
			int sx, sy, dx, dy;
			bool clip;

			if (bmp == null)
				return;

			clip = bmp.clip;

			if (clip)
			{
				sx = x - rx - 1;
				sy = y - ry - 1;
				dx = x + rx + 1;
				dy = y + ry + 1;

				if ((sx >= bmp.cr) || (sy >= bmp.cb) || (dx < bmp.cl) || (dy < bmp.ct))
					return;

				if ((sx >= bmp.cl) && (sy >= bmp.ct) && (dx < bmp.cr) && (dy < bmp.cb))
					clip = false;
			}

			if (rx < 1)
				rx = 1;

			if (ry < 1)
				ry = 1;

			if (rx > ry)
			{
				dc = -1;
				dd = 0xFFFF;
				ix = 0;
				iy = rx * 64;
				na = 0;
				nb = (iy + 32) >> 6;
				nc = 0;
				nd = (nb * ry) / rx;

				do
				{
					a = na;
					b = nb;
					c = nc;
					d = nd;

					ix = ix + (iy / rx);
					iy = iy - (ix / rx);
					na = (ix + 32) >> 6;
					nb = (iy + 32) >> 6;
					nc = (na * ry) / rx;
					nd = (nb * ry) / rx;

					if ((c > dc) && (c < dd))
					{
						hfill(bmp, x - b, y + c, x + b, color, clip);

						if (c != 0)
							hfill(bmp, x - b, y - c, x + b, color, clip);

						dc = c;
					}

					if ((d < dd) && (d > dc))
					{
						hfill(bmp, x - a, y + d, x + a, color, clip);
						hfill(bmp, x - a, y - d, x + a, color, clip);
						dd = d;
					}

				} while (b > a);
			}
			else
			{
				da = -1;
				db = 0xFFFF;
				ix = 0;
				iy = ry * 64;
				na = 0;
				nb = (iy + 32) >> 6;
				nc = 0;
				nd = (nb * rx) / ry;

				do
				{
					a = na;
					b = nb;
					c = nc;
					d = nd;

					ix = ix + (iy / ry);
					iy = iy - (ix / ry);
					na = (ix + 32) >> 6;
					nb = (iy + 32) >> 6;
					nc = (na * rx) / ry;
					nd = (nb * rx) / ry;

					if ((a > da) && (a < db))
					{
						hfill(bmp, x - d, y + a, x + d, color, clip);

						if (a != 0)
							hfill(bmp, x - d, y - a, x + d, color, clip);

						da = a;
					}

					if ((b < db) && (b > da))
					{
						hfill(bmp, x - c, y + b, x + c, color, clip);
						hfill(bmp, x - c, y - b, x + c, color, clip);
						db = b;
					}

				} while (b > a);
			}
		}

		/* do_arc:
		 *  Helper function for the arc function. Calculates the points in an arc
		 *  of radius r around point x, y, going anticlockwise from angle ang1 to ang2,
		 *  and calls the specified routine for each one. d
		 *  The output proc will be passed first a copy of the bmp parameter, then 
		 *  the x, y point, then a copy of the d parameter (so putpixel() can be 
		 *  used as the callback).
		 */
		
		//TODO: optimize - remove cos/sin from loop
		static public void do_arc(BITMAP bmp, int x, int y, double a1, double a2, int r, int color, draw_callback callback)
		{
			double start_rad = a1 * (System.Math.PI / 180.0);
			double length_rad = (a2 - a1) * (System.Math.PI / 180.0);
			double arc_length = r * length_rad;

			if (arc_length < 0.0)
				arc_length *= -1.0;

			int segments = Convert.ToInt32(System.Math.Round(arc_length));

			if (segments == 0)
				segments = 1;

			int points = segments + 1;
			int _x;
			int _y;
			double angle_rad;

			for (int q = 0; q < points; q++)
			{
				angle_rad = start_rad + ((double)q / points) * length_rad;

				_x = Convert.ToInt32(x + r * System.Math.Cos(angle_rad));
				_y = Convert.ToInt32(x + r * System.Math.Sin(angle_rad));

				callback(bmp, _x, _y, color);
			}
		}
		
		/* arc:
		 *  Draws an arc.
		*/
		static public void arc(BITMAP bmp, int x, int y, double ang1, double ang2, int r, int color)
		{
			do_arc(bmp, x, y, ang1, ang2, r, color, putpixel);
		}

		/* floodfill:
		 *  Fills an enclosed area (starting at point x, y) with the specified color.
		 */
		static public void floodfill(BITMAP dst, int dx, int dy, int color)
		{
			if (dst == null)
				return;

			floodfill(dst, dx, dy, color, getpixel(dst, dx, dy));
		}

		/* floodfill:
		 *  Fills an enclosed area (starting at point x, y) with the specified color, replacing seed_color.
		 *  
		 * TODO: optimize
		 */
		static public void floodfill(BITMAP dst, int dx, int dy, int color, int seed_color)
		{
			if (dst == null)
				return;

			if (dy < 0 || dy > dst.h - 1 || dx < 0 || dx > dst.w - 1)
				return;

			Stack<int> stack = new Stack<int>();
			stack.Push(dx);
			stack.Push(dy);

			int x;
			int y;
			int val;

			while (stack.Count > 0)
			{
				y = stack.Pop();
				x = stack.Pop();

				if (y < 0 || y > dst.h - 1 || x < 0 || x > dst.w - 1)
					continue;

				val = getpixel(dst, x, y);

				if (val == seed_color)
				{
					putpixel(dst, x, y, color);

					stack.Push(x + 1);
					stack.Push(y);

					stack.Push(x - 1);
					stack.Push(y);

					stack.Push(x);
					stack.Push(y + 1);

					stack.Push(x);
					stack.Push(y - 1);
				}
			}
		}
	}
}
